package abc.filter;

import abc.model.ValidationInfo;
import abc.exception.ParamEmptyException;
import abc.exception.ParamException;
import abc.utils.JacksonUtil;
import abc.utils.StringUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.RouteMatcher;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


@Data
@Slf4j
@Component
public class ValidationHandler {
    /**
     * httpheader，traceId的key名称
     */
    private static final String REQUESTID = "traceId";

    private static final String CONTENT_TYPE = "Content-Type";

    private static final String CONTENT_TYPE_JSON = "application/json";

    private static final String GATEWAY_ROUTE_BEAN = "org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRoute";

    private final ThreadLocal localBodyMap = new ThreadLocal<>();

    @Autowired
    private ValidationProperty validationProperty;

    private void checkRouter(ServerWebExchange exchange) {
        RouteMatcher.Route route = exchange.getAttribute(GATEWAY_ROUTE_BEAN);
        //是否配置路由
        if (route != null) {
            log.info("route has been ready!!!");
        }
    }

    public Mono<Void> filter(ServerWebExchange exchange, Object chain) {
        MediaType contentType = exchange.getRequest().getHeaders().getContentType();
        if (contentType != null && MediaType.APPLICATION_JSON.isCompatibleWith(contentType)
                && exchange.getRequest().getHeaders().getContentLength() > 0L) {
            return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
                DataBufferUtils.retain(dataBuffer);
                CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.toByteBuffer());
                DataBufferUtils.release(dataBuffer);
                String bodyString = charBuffer.toString();
                if(StringUtil.isNotEmpty(bodyString)) {
                    if(Pattern.matches("\\[[\\w\\W]+\\]", bodyString)) {
                        try {
                            List<Map> list = JacksonUtil.toList(bodyString, Map.class);
                            localBodyMap.set(list);
                            for(Map map : list) {
                                verify(map, exchange.getRequest().getMethod());
                            }
                        } catch (IOException e) {
                            log.error(e.getMessage(), e);
                        }

                    }
                    else {
                        localBodyMap.set(JacksonUtil.parse(bodyString, Map.class));
                        verify((Map<String, Object>) localBodyMap.get(), exchange.getRequest().getMethod());
                    }
                }
                checkRouter(exchange);
                Flux<DataBuffer> cacheFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
                ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                    @Override
                    public Flux<DataBuffer> getBody() {
                        return cacheFlux;
                    }
                };

                if(chain instanceof WebFilterChain) {
                    return ((WebFilterChain) chain).filter(exchange.mutate().request(mutatedRequest).build());
                }
                else {
                    return ((GatewayFilterChain) chain).filter(exchange.mutate().request(mutatedRequest).build());
                }
            });
        }

        if(chain instanceof WebFilterChain) {
            return ((WebFilterChain) chain).filter(exchange);
        }
        else {
            return ((GatewayFilterChain) chain).filter(exchange);
        }
    }

    private void verify(Map<String, Object> data, HttpMethod method) {
        if(data == null || validationProperty.getValidation() == null) {
            return;
        }

        for(String key : data.keySet()) {
            List<ValidationInfo> list = validationProperty.getValidation()
                    .stream().filter(a -> a.getName().equals(key))
                    .collect(Collectors.toList());
            if(list.size()<1) {
                continue;
            }
            for(ValidationInfo info : list) {
                if(StringUtil.isNotEmpty(info.getMethods())) {
                    List<String> methodList = StringUtil.splitString2List(info.getMethods().toLowerCase(), ",");
                    if(!methodList.contains(method.name().toLowerCase())) {
                        continue;
                    }
                }
                if(info.getRequire() == null) {
                    info.setRequire("true");
                }
                if("true".equals(info.getRequire()) &&
                        (data.get(key)==null || StringUtil.isEmpty(data.get(key).toString()))) {
                    throw new ParamEmptyException();
                }
                if(StringUtil.isNotEmpty(info.getPattern()) &&
                        !Pattern.matches(info.getPattern(), data.get(key).toString())) {
                    if(StringUtil.isEmpty(info.getMessage())) {
                        throw new ParamException();
                    }
                    throw new ParamException(info.getMessage());
                }
            }
        }
    }
}
